#include "common.h"
#include "memory/cache.h"
#include "nemu.h"

uint32_t dram_read(hwaddr_t, size_t);
void dram_write(hwaddr_t, size_t, uint32_t);

static lnaddr_t seg_translate(swaddr_t addr, uint8_t sreg) {
	uint32_t base = cpu.sreg[sreg].base;
	uint32_t offset = addr;
	return base + offset;
}

static hwaddr_t page_translate(lnaddr_t addr) {

	int index = tlb_read(addr);
	if (index != -1) {
		return (tlb[index].page_info << 12) + (addr & 0xfff);
	}

	uint32_t dir = addr >> 22;
	uint32_t page = (addr >> 12) & 0x3ff;
	uint32_t offset = addr & 0xfff;

	uint32_t dir_base = cpu.cr3.page_directory_base;
	PageDescriptor dir_entry;
	dir_entry.val = hwaddr_read((dir_base << 12) + (dir << 2), 4);

	Assert(dir_entry.p == 1, "Page directory entry not present");

	uint32_t page_base = dir_entry.base;
	PageDescriptor page_entry;
	page_entry.val = hwaddr_read((page_base << 12) + (page << 2), 4);
	Assert(page_entry.p == 1, "Page table entry not present");

	hwaddr_t hwaddr = (page_entry.base << 12) + offset;

	tlb_write(addr, hwaddr);

	return hwaddr;
}

hwaddr_t page_translate_cmd(lnaddr_t addr, bool *success) {

	if (cpu.cr0.paging == 0 || cpu.cr0.protect_enable == 0) {
		*success = true;
		return addr;
	}

	uint32_t dir = addr >> 22;
	uint32_t page = (addr >> 12) & 0x3ff;
	uint32_t offset = addr & 0xfff;

	uint32_t dir_base = cpu.cr3.page_directory_base;
	PageDescriptor dir_entry;
	dir_entry.val = hwaddr_read((dir_base << 12) + (dir << 2), 4);

	if (dir_entry.p == 0) {
		*success = false;
		return -1;
	}

	uint32_t page_base = dir_entry.base;
	PageDescriptor page_entry;
	page_entry.val = hwaddr_read((page_base << 12) + (page << 2), 4);
	
	if (page_entry.p == 0) {
		*success = false;
		return -2;
	}

	hwaddr_t hwaddr = (page_entry.base << 12) + offset;
	return hwaddr;
}

/* Memory accessing interfaces */

uint32_t hwaddr_read(hwaddr_t addr, size_t len) {
	return cache1_read(addr, len);
	// return dram_read(addr, len) & (~0u >> ((4 - len) << 3));
}

void hwaddr_write(hwaddr_t addr, size_t len, uint32_t data) {
	cache1_write(addr, len, data);
	// dram_write(addr, len, data);
}

uint32_t lnaddr_read(lnaddr_t addr, size_t len) {
	if (cpu.cr0.protect_enable == 1 && cpu.cr0.paging == 1) {
		uint32_t offset = addr & 0xfff;
		if (offset + len > 0xfff + 1) {
			// Assert(0, "Cross page boundary");
			size_t len1 = 0xfff + 1 - offset;
			size_t len2 = len - len1;
			uint32_t data1 = lnaddr_read(addr, len1);
			uint32_t data2 = lnaddr_read(addr + len1, len2);
			return (data2 << (len1 << 3)) + data1;
		} else {
			hwaddr_t hwaddr = page_translate(addr);
			return hwaddr_read(hwaddr, len);
		}
	} else return hwaddr_read(addr, len);
}

void lnaddr_write(lnaddr_t addr, size_t len, uint32_t data) {
	if (cpu.cr0.protect_enable == 1 && cpu.cr0.paging == 1) {
		uint32_t offset = addr & 0xfff;
		if (offset + len > 0xfff + 1) {
			// Assert(0, "Cross page boundary");
			size_t len1 = 0xfff + 1 - offset;
			size_t len2 = len - len1;
			uint32_t data1 = data & ((1 << (len1 << 3)) - 1);
			uint32_t data2 = data >> (len1 << 3);
			lnaddr_write(addr, len1, data1);
			lnaddr_write(addr + len1, len2, data2);
		} else {
			hwaddr_t hwaddr = page_translate(addr);
			hwaddr_write(hwaddr, len, data);
		}
	} else hwaddr_write(addr, len, data);
}

uint32_t swaddr_read(swaddr_t addr, size_t len, uint8_t sreg) {
#ifdef DEBUG
	assert(len == 1 || len == 2 || len == 4);
#endif

	if (cpu.cr0.protect_enable == 1) {
		lnaddr_t lnaddr = seg_translate(addr, sreg);
		return lnaddr_read(lnaddr, len);
	} else return lnaddr_read(addr, len);
}

void swaddr_write(swaddr_t addr, size_t len, uint32_t data, uint8_t sreg) {
#ifdef DEBUG
	assert(len == 1 || len == 2 || len == 4);
#endif

	if (cpu.cr0.protect_enable == 1) {
		lnaddr_t lnaddr = seg_translate(addr, sreg);
		lnaddr_write(lnaddr, len, data);
	} else {
		lnaddr_write(addr, len, data);
	}
}

